const path = require('path')
const fs = require('fs')
const execa = require('execa')
const chalk = require('chalk')
const stepOK = msg => console.log(chalk.greenBright(msg))

const isFunction = val => toString.call(val) === '[object Function]'
const isObject = val => toString.call(val) === '[object Object]'
const isSameType = (val1, val2) => toString.call(val1) === toString.call(val2)
const mergeOptions = (baseOptions, toMergeOptions) => {
  let mergedOptions = Object.assign({}, baseOptions)
  for (const key in toMergeOptions) {
    const exist = mergedOptions[key]
    const incoming = toMergeOptions[key]
    // 若选项类型不一致，简单处理：不合并
    if (exist && incoming && !isSameType(exist, incoming)) continue
    if (!exist && incoming) {
      mergedOptions[key] = incoming
      continue
    }
    if (exist && incoming) {
      if (isFunction(exist)) {
        mergedOptions[key] = function(...args) {
          exist(...args)
          incoming(...args)
        }
      } else if (isObject(exist)) {
        mergedOptions[key] = mergeOptions(exist, incoming)
      }
    }
  }
  return mergedOptions
}

const mergeVueConfigFromBase = toMergedConfigOptions => {
  const baseConfig = require(path.resolve(__dirname, '../vue.config'))
  return mergeOptions(baseConfig, toMergedConfigOptions)
}

const bin = name => path.resolve(__dirname, '../node_modules/.bin/' + name)
const run = (bin, args, opts = {}) => execa(bin, args, { stdio: 'inherit', ...opts })

/**
 * 搜集指定目录下的包
 * @param {String} directory 要搜集的目录路径
 * @return {{path:string,value:Record<string,any>,directory:string}} 返回值
 */
function collectPackages(directory) {
  let packages = []
  const files = fs.readdirSync(directory)
  files.forEach(shortname => {
    const currentPath = path.join(directory, shortname)
    // 非目录的不处理
    if (!fs.statSync(currentPath).isDirectory()) return
    const pkgPath = path.join(currentPath, `package.json`)
    const pkgExists = fs.existsSync(pkgPath)
    if (pkgExists) {
      const pkg = require(pkgPath)
      packages.push({ path: currentPath, value: pkg, directory: shortname })
    } else {
      const monorepoPath = path.join(currentPath, `.monorepo`)
      const isMonorepo = fs.existsSync(monorepoPath)
      if (!isMonorepo) return false
      const monoRepoDirectory = currentPath
      packages = packages.concat(collectPackages(monoRepoDirectory))
    }
  })
  return packages
}

/**
 * 并行执行某个相同操作，每个操作仅仅是参数(source)不同，执行的方法(iteratorFn)都是相同的
 * @param {Number} maxConcurrency 并发数量
 * @param {Array} params 执行的参数数组
 * @param {Function} iteratorFn 执行的函数
 * @returns
 */
async function runParallel(maxConcurrency, params, iteratorFn) {
  const ret = []
  const executing = []
  for (const param of params) {
    const p = Promise.resolve().then(() => iteratorFn(param))
    ret.push(p)

    if (maxConcurrency <= params.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1))
      executing.push(e)
      if (executing.length >= maxConcurrency) {
        await Promise.race(executing)
      }
    }
  }
  return Promise.all(ret)
}

const rmdirSync = (function() {
  function iterator(url, dirs) {
    var stat = fs.statSync(url)
    if (stat.isDirectory()) {
      dirs.unshift(url) //收集目录
      inner(url, dirs)
    } else if (stat.isFile()) {
      fs.unlinkSync(url) //直接删除文件
    }
  }
  function inner(path, dirs) {
    var arr = fs.readdirSync(path)
    for (var i = 0, el; (el = arr[i++]); ) {
      iterator(path + '/' + el, dirs)
    }
  }
  return function(dir, cb) {
    cb = cb || function() {}
    var dirs = []

    try {
      iterator(dir, dirs)
      for (var i = 0, el; (el = dirs[i++]); ) {
        fs.rmdirSync(el) //一次性删除所有收集到的目录
      }
      cb()
    } catch (e) {
      //如果文件或目录本来就不存在，fs.statSync会报错，不过我们还是当成没有异常发生
      e.code === 'ENOENT' ? cb() : cb(e)
    }
  }
})()

exports.bin = bin
exports.run = run
exports.runParallel = runParallel
exports.collectPackages = collectPackages
exports.rmdirSync = rmdirSync
exports.stepOK = stepOK
exports.mergeOptions = mergeOptions
exports.mergeVueConfigFromBase = mergeVueConfigFromBase
